#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <semaphore.h>
#include <string.h>
#include <time.h>
#include "../model/filme.h"
#include "../model/req.h"
#include "servers.h"
#include "database.h"
#include "fifo.h"

char fifo_name[] = "server1-fifo.data";
char db[] = "server1-database.data";
sem_t sem_fifo;
sem_t sem_db;

/**
 * readFifo
 * Lê a FIFO e retorna a conexão.
 */
void readFifo() {
    tFifo *inst;
    int fda[2], sock;
    tFilme *f;
    while (1) {
        sem_wait(&sem_fifo);
        inst = removeFifo(fifo_name);
        sem_post(&sem_fifo);
        if (inst == NULL) {
            usleep(20000);
            continue;
        }
        printf("%s - %s\n", inst->package, get_cmd(inst->package));
        if (!strcmp(get_cmd(inst->package), "add")) {
            sem_wait(&sem_db);
            inserirDatabase(db, *(charToFilme(inst->package)));
            sem_post(&sem_db);
            printf("Cadastro Adicionado!\n");
            continue;
        }
        printf("Conectado?\n");
        if (!connectInter(&sock, &(inst->addr))) continue;
        printf("Conectado\n");

        while (pipe(fda) == -1) {
            perror("pipe");
        }
        f = charToFilme(inst->package);
        if (!fork()) {
            close(1);
            dup(fda[1]);
            close(fda[1]);
            close(fda[0]);
            sem_wait(&sem_db);
            if (strlen(f->titulo) > 0) {
                consultaDatabase(db, "titulo", f->titulo, f->ano);
            } else if (strlen(f->ator) > 0){
                consultaDatabase(db, "ator", f->ator, f->ano);
            } else if (strlen(f->genero) > 0){
                consultaDatabase(db, "genero", f->genero, f->ano);
            } else {                
                consultaDatabase(db, "ano", f->titulo, f->ano);
            }
            sem_post(&sem_db);
            exit(0);
        }
        if (!fork()) {
            char resp[400];
            close(0);
            dup(fda[0]);
            close(fda[1]);
            close(fda[0]);
            while (scanf("%s", resp) > 0) {
                strcat(resp, inet_ntoa(inst->addr.sin_addr));
                memset(&resp[strlen(resp)], '\0', 400 - strlen(resp));
                printf("%s -> sending...\n", resp);
                enviar(sock, resp);
            }
            exit(0);
        }
        close(fda[0]);
        close(fda[1]);
        printf("Consulta Feita!");
        close(sock);
        while (wait(NULL) > 0);
    }
    while (wait(NULL) > 0);
}

/**
 * connectInter: realiza a conexão entre dois endereços
 * @param sock: socket de conexão
 * @param inter: endereço de socket intermediário
 * @return 0 se sucesso, 1 se fracasso.
 */
int connectInter(int *sock, sockaddr_in *inter) {
    hostent *he;
    if (!preparar(sock, inter, he, 10500)) return 0;
    if (!conectar(*sock, inter)) return 0;
    return 1;
}

/**
 * Servidor 1 - Recebe as conexões na
 * @param agrc - quantidade de parâmetros
 * @param argv - vetor de parâmetros
 * @return 0 em caso de sucesso.
 */
int main(int agrc, char *argv[]) {
    int sock, sock_req, addr_in_size, bytes_recv;
    sockaddr_in req_addr, serv_addr;
    char package[400];

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[1]));
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    memset(&(serv_addr.sin_zero), '\0', 8);

    sem_init(&sem_fifo, 0, 1);
    sem_init(&sem_db, 0, 1);

    if (bind(sock, (sockaddr *) & serv_addr, sizeof (sockaddr)) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(sock, BACKLOG_SERV) == -1) {
        perror("listen");
        exit(1);
    }

    initFifo(fifo_name);
    initDatabase(db);

    if (!fork()) {
        readFifo();
        exit(0);
    }

    while (1) {
        addr_in_size = sizeof (sockaddr_in);
        if ((sock_req = accept(sock, (sockaddr *) & req_addr, &addr_in_size)) == -1) {
            perror("accept");
            continue;
        }

        if (!fork()) {
            close(sock);
            while ((bytes_recv = recv(sock_req, package, sizeof (package), 0)) == -1) {
                perror("recv");
            }
            printf("Signal received from %s\n", inet_ntoa(req_addr.sin_addr));
            package[bytes_recv] = '\0';
            sem_wait(&sem_fifo);
            addFifo(fifo_name, package, req_addr);
            sem_post(&sem_fifo);

            close(sock_req);
            exit(0);
        }
        close(sock_req);
    }

    while (wait(NULL) > 0);
    return 0;
}